Skip to content

feat(desktop): restore Workspaces tab with host-scoped workspace list - #5908

Merged
saddlepaddle merged 3 commits into
mainfrom
restore-workspaces-tab
Jul 24, 2026
Merged

saddlepaddle merged 3 commits into
mainfrom
restore-workspaces-tab

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Brings the Workspaces sidebar tab back for real (no flag, no setting) and reworks the /v2-workspaces page to be host-first: the device picker loads instantly from the hosts collections, and the workspace list always reads from exactly one explicitly-targeted host — no fan-out, no reliance on the cloud v2_workspaces table being up to date.

Closes #5881. Supersedes #5901 (which kept the tab behind an off-by-default Appearance toggle).

Changes

Tab restored — SHOW_WORKSPACES_TAB deleted from DashboardSidebarHeader; both the collapsed-rail icon and the expanded row render unconditionally.

Single-host reads — useHostWorkspacesSource now takes an optional scopedHostId: the page passes the selected host and gets one workspace.list (local direct, remote via relay), live-patched by that host's workspace:changed events, with the per-host IndexedDB snapshot for offline and deliberately no Electric v2Workspaces fallback — an unreachable host shows its last-seen snapshot or nothing, never stale cloud rows. No new hook: scoped calls reuse the fan-out's target derivation, query keys, snapshots, and event patching, so where the provider is mounted the host is fetched once, not twice.

Always host-scoped — the "All devices" merged view is removed; the device filter defaults to This device. Ten idle/slow hosts can no longer delay the page or silently thin out the list: readiness and failure are per selected host.

Instant host picker — options come from v2Hosts ∩ v2UsersHosts (locally cached Electric data), so every accessible host appears immediately, including hosts with zero workspaces. Per-host workspace counts are dropped — counting other hosts' workspaces would itself be a fan-out.

Host-served rows are their own proof — the join previously required a v2Hosts row and a v2UsersHosts row before rendering a workspace, so an empty/stale cloud host table hid live host data (caught during CDP verification below). That gate now applies only to cloud-fallback rows; host name/online status degrade gracefully when the cloud row is missing.

Fan-out trims — PR-status enrichment (pullRequests.getByWorkspaces, 10s interval) now runs only for the host-scoped page; unscoped callers (command palette search, dev sidebar seeding) don't render PR data and previously triggered it per host. Those callers keep the shared provider's merged view via the hook's no-deviceFilter path.

Empty vs loading — the empty state is gated on the selected host's settlement, so an unanswered host reads as loading, not "No workspaces yet".

Out of scope: the app-wide HostWorkspacesProvider fan-out still exists — the dashboard sidebar and palette genuinely need cross-host data. Taking those off the fan-out is its own ticket.

Testing

  • bun run lint and bun run --cwd apps/desktop typecheck clean.
  • CDP end-to-end against this branch's dev app (renderer on the workspace's Vite port, session verified via the app's bearer auth client with an active org): real mouse click on the restored tab navigated #/workspace/<id> → #/v2-workspaces; page rendered 27 workspaces host-scoped to "This device" with the picker defaulting correctly; navigate-away/back and a full reload both re-rendered the populated list. Screenshots captured at each step.
  • The verification run caught the cloud-host-table join bug live: the local host served 27 rows while the page showed "No workspaces yet" because v2Hosts was empty in that environment — fixed (host-served rows bypass the cloud gate) and re-verified populated after remount and reload.
  • Not exercised: remote-host selection (no remote hosts in the dev environment) and the visual loading state (the local host answers too fast).

https://claude.ai/code/session_01ULuSGAMv3cqtqBMjaYzDqd


Summary by cubic

Restores the Workspaces tab and makes the Workspaces page host-scoped for faster loads and clearer states. Folds single-host reads into useHostWorkspacesSource and simplifies filters and outputs.

  • New Features

    • Workspaces tab is back in the sidebar (flag removed).
    • Host-scoped list via useHostWorkspacesSource(scopedHostId?): single workspace.list, live workspace:changed updates, IndexedDB snapshot for offline; no cloud v2Workspaces fallback when scoped.
    • Device filter defaults to “This device”; removed “All devices.”
    • Host picker loads instantly from cached v2Hosts/v2UsersHosts (no per-host counts).
  • Refactors

    • Unified source hook: undefined fans out once in the provider; a host id targets one host; null defers and runs nothing. Scoped calls share query keys; avoid unscoped duplication outside the provider.
    • Simplified useAccessibleV2Workspaces: removed pinned/others and device counts; now returns isReady to gate empty states (list waits for the selected host to settle).
    • Host-served rows no longer depend on cloud host tables; names/online status degrade gracefully. PR enrichment runs only on the host‑scoped page; unscoped callers skip PR requests.

Written for commit 227f853. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Restored the Workspaces tab in the dashboard sidebar (now shown unconditionally).
    • Workspaces now default to the current device (“This device”) rather than “All devices”.
  • Bug Fixes
    • Prevented empty-state content from showing until workspace data is ready.
    • Improved device/host labeling and fallback behavior when device info is missing or stale, including better handling of offline/unavailable devices.
  • Style
    • Simplified device filter UI by removing workspace/device count displays.
  • Documentation
    • Updated the dashboard sidebar note for the Workspaces tab behavior.

Bring back the sidebar Workspaces entry (removes the SHOW_WORKSPACES_TAB
flag) and rework /v2-workspaces to read from a single explicitly-targeted
host instead of the all-hosts fan-out:

- New useSelectedHostWorkspaces: one workspace.list against the selected
  host, live-patched by its workspace:changed events, IndexedDB snapshot
  for offline, no Electric v2Workspaces fallback.
- Device filter defaults to "This device"; the all-devices merged view is
  removed. Host picker is populated instantly from the v2Hosts and
  v2UsersHosts collections (no per-host counts - counting is a fan-out).
- Host-served rows no longer require v2Hosts/v2UsersHosts rows to render;
  the cloud-host-table gate applies only to cloud-fallback rows.
- PR enrichment runs only for the host-scoped page, not for unscoped
  callers (command palette, dev sidebar seeding).
- Empty state is gated on the selected host's settlement so an unanswered
  host reads as loading, not empty.

Claude-Session: https://claude.ai/code/session_01ULuSGAMv3cqtqBMjaYzDqd
@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Workspaces sidebar entry is restored unconditionally. V2 workspaces now default to the current device, remove device counts, support scoped host loading, expose readiness state, derive host options from cached hosts, and gate empty-state rendering until data is ready.

Changes

Workspace navigation and host-scoped data flow

Layer / File(s) Summary
Restore Workspaces sidebar navigation
apps/desktop/plans/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/...
The Workspaces entry is rendered in both collapsed and expanded sidebar states without SHOW_WORKSPACES_TAB.
Update device filter and display contracts
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/stores/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesHeader/...
The default device filter is “This device”; the “All devices” branch and numeric device counts are removed from the filter UI and component contracts.
Load selected-host workspaces
apps/desktop/src/renderer/hooks/host-workspaces/..., apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/...
useHostWorkspacesSource accepts an optional host scope, filters query targets accordingly, omits cloud fallback when scoped, and computes scope-aware readiness.
Integrate scoped rows and readiness
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/..., apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/...
The accessibility hook selects scoped or unscoped sources, derives host options from cached host rows, returns isReady instead of counts, and the page/list use readiness to control empty states.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant V2WorkspacesPage
  participant useAccessibleV2Workspaces
  participant useHostWorkspacesSource
  participant HostWorkspaces
  V2WorkspacesPage->>useAccessibleV2Workspaces: request filtered workspace data
  useAccessibleV2Workspaces->>useHostWorkspacesSource: provide selected host id
  useHostWorkspacesSource->>HostWorkspaces: query matching host targets
  HostWorkspaces-->>useHostWorkspacesSource: workspace rows and query state
  useHostWorkspacesSource-->>useAccessibleV2Workspaces: scoped rows and isReady
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Beyond restoring the sidebar tab, the PR also refactors the v2-workspaces host-scoped data flow, filters, and hook contracts, which are outside #5881. Split the sidebar-tab fix from the host-scoped workspace-list refactor, or link a broader issue covering the additional changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR restores the missing Workspaces sidebar item, which directly satisfies issue #5881.
Title check ✅ Passed The title is concise, conventional, and clearly summarizes the main change: restoring the Workspaces tab with host-scoped listing.
Description check ✅ Passed It includes a clear summary, detailed changes, and testing notes, covering the template’s core requirements despite not matching every heading.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch restore-workspaces-tab

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Delete useSelectedHostWorkspaces - it duplicated the source hook's target
derivation, snapshots, query config, and event patching. The source hook
now takes an optional scopedHostId (undefined = fan-out, machine id =
single host with no cloud fallback, null = scoped but unresolved, runs
nothing). Also drop the now-dead device-filter stage (scoped rows are
already single-host) and the duplicated empty-state wrapper.

Claude-Session: https://claude.ai/code/session_01ULuSGAMv3cqtqBMjaYzDqd

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts`:
- Around line 68-70: The per-host snapshot hydration and workspace:changed
listener ownership currently runs once per useHostWorkspacesSource instance,
causing duplicate persistence and event patching for scoped and unscoped
consumers. In useHostWorkspacesSource, move or share this lifecycle work through
provider-backed ownership so each host has only one hydrator and retained
listener; update the guidance in HostWorkspacesProvider to clarify that scoped
calls are not inherently safe for non-query lifecycle work. Affected sites:
apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts
lines 68-70 require the lifecycle deduplication;
apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx
lines 14-15 require the guidance update.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cca1312a-ce40-4ce5-b916-1cb1cc98d749

📥 Commits

Reviewing files that changed from the base of the PR and between c88742c and 6a6dad3.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/V2WorkspacesList.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts

Comment on lines +68 to +70
export function useHostWorkspacesSource(
scopedHostId?: string | null,
): UseHostWorkspacesResult {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm simultaneous unscoped/provider and scoped/page source-hook usage.
rg -n -C3 --type ts 'useHostWorkspacesSource\s*\(' apps/desktop/src/renderer

# Inspect the source-local listener and retain lifecycle.
rg -n -C5 --type ts '"workspace:changed"|\.retain\(\)|getEventBus' \
  apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts

Repository: superset-sh/superset

Length of output: 4436


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== useHostWorkspaces outline =="
ast-grep outline apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts --view expanded || true

echo "== source-hook key/runtime sections =="
cat -n apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts | sed -n '1,260p'

echo "== HostWorkspacesProvider =="
cat -n apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx | sed -n '1,80p'

echo "== v2Workspaces relevant source =="
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts | sed -n '220,260p'

echo "== query key implementation =="
rg -n -C4 --type ts 'function getHostWorkspacesQueryKey|const getHostWorkspacesQueryKey|`@tanstack/react-query`|useQueries|useQueryClient|useLiveQuery|retain\(\)' apps/desktop/src/renderer/hooks/host-workspaces apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces

Repository: superset-sh/superset

Length of output: 38368


Deduplicate scoped source per-host lifecycle work.

HostWorkspacesProvider mounts an unscoped useHostWorkspacesSource() while useAccessibleV2Workspaces also mounts useHostWorkspacesSource(selectedHostId) for the selected host. Shared query keys coalesce results, but each hook instance hydrates the IndexedDB snapshot and registers its own workspace:changed listener with bus.retain(), so selected-host events are patched and persisted twice.

  • apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts#L68-L70: move per-host snapshot/event ownership into a shared provider-backed source, or otherwise share it across scoped and unscoped consumers.
  • apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx#L14-L15: update the guidance to avoid implying scoped calls are fully safe for non-query lifecycle work.
📍 Affects 2 files
  • apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts#L68-L70 (this comment)
  • apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx#L14-L15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts`
around lines 68 - 70, The per-host snapshot hydration and workspace:changed
listener ownership currently runs once per useHostWorkspacesSource instance,
causing duplicate persistence and event patching for scoped and unscoped
consumers. In useHostWorkspacesSource, move or share this lifecycle work through
provider-backed ownership so each host has only one hydrator and retained
listener; update the guidance in HostWorkspacesProvider to clarify that scoped
calls are not inherently safe for non-query lifecycle work. Affected sites:
apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.ts
lines 68-70 require the lifecycle deduplication;
apps/desktop/src/renderer/routes/_authenticated/providers/HostWorkspacesProvider/HostWorkspacesProvider.tsx
lines 14-15 require the guidance update.

pinned/others/V2WorkspaceProjectGroup had no consumers - delete them.
Collapse the scoped-vs-fanout selection into one destructure with a
comment explaining why both hooks always run, and document that a null
scopedHostId runs nothing.

Claude-Session: https://claude.ai/code/session_01ULuSGAMv3cqtqBMjaYzDqd
@saddlepaddle
saddlepaddle merged commit 4edf951 into main Jul 24, 2026
11 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

saddlepaddle added a commit that referenced this pull request Jul 24, 2026
Main (#5908) replaced counts with isReady in useAccessibleV2Workspaces —
textually clean merge but semantically broken (the pinned empty-state
check referenced the removed counts). Resolved by gating the pinned
create screen on isReady + zero rows + default filters, which also fixes
the transient-zero flash the old counts check was exposed to.

Claude-Session: https://claude.ai/code/session_01SfU6AHAcHwrjwXwUFUDth5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Workspaces nav item missing from left sidebar

1 participant